<?php
/*
 * @Author: your name
 * @Date: 2020-06-05 17:32:41
 * @LastEditTime: 2020-12-24 10:58:56
 * @LastEditors: Please set LastEditors
 * @Description: 客户端注册逻辑
 * @FilePath: /special/application/api/service/ClientRegisterService.php
 */
namespace app\api\service\client_register;

use app\adminapiv3\model\HostModuleRelationModel;
use app\adminapiv3\model\learn_data\HostLearnDataModel;
use app\adminapiv3\model\ServerAuthModuleModel;
use app\common\exception\CustomException;
use app\common\model\access_log\HostAccessLogModel;
use think\Db;
use think\Exception;

class ClientRegisterService
{
    public $host_id = 0;//主机id
    public $group_id = 0;//组id
    public $host_key = 0;//主机hostkey
    private $ipaddr = [];//IP地址
    private $mac = [];//mac地址
    private $network_card = [];//网卡信息
    private $first_ipaddr;//第一个网卡信息
    private $is_reinstall = false; //是否是重新安装
    private $hostInfo; //主机硬件信息
    private $host_info; //主机数据

    /**
     * @name: 注册
     * @Param:
     * @Description:
     * @Author: foo
     * @Date: 2019-11-07 11:27:25
     */
    public function reg ($registerData)
    {

        if (!isset($registerData['networkcard']) || !count($registerData['networkcard']))
        {
            throw new CustomException('注册时失败:缺少网卡信息', 422);
        }

        $this->networkcard($registerData['networkcard']);

        if (!isset($registerData['host_info']))
        {
            throw new CustomException('注册时失败:缺少主机硬件信息', 422);
        }

        $this->hostInfo($registerData['host_info']);

        //可以传空，但字段必须存在
        if (!isset($registerData['hostkey']))
        {
            throw new CustomException('注册时失败:缺少主机绑定信息', 422);
        }

        //可以传空，但字段必须存在
        if (!isset($registerData['group']))
        {
            throw new CustomException('注册时失败:缺少资产组绑定码', 422);
        }

        //资产绑定
        $this->hostKeyAndGroup($registerData['hostkey'],$registerData['group']);


        if ($this->is_reinstall)
        {//重新注册,修改基本信息
            $host = [
                'alias' => empty($this->host_info['alias']) ? $registerData['alias'] : $this->host_info['alias'],
                'system_name' => empty($this->host_info['system_name']) ? $registerData['os'] : $this->host_info['system_name'],
                'client_version' => $registerData['version'],
                'os' => stristr($registerData['os'], 'windows') ? 1 : 2,
                'group_id' => $this->group_id,
            ];


            //修改host数据表信息
            Db::name('host')->where('id', $this->host_id)->update($host);

            //修改网卡信息
            Db::name('host_contact')->where('host_id', $this->host_id)->delete();
            $network_card = array_map(function($v){
                $v['host_id'] = $this->host_id;
                return $v;
            }, $this->network_card);

            Db::name('host_contact')->where('host_id', $this->host_id)->insertAll($network_card);

            return;
        }

        $authModuleModel = new ServerAuthModuleModel();
        $relationModel = new HostModuleRelationModel();

        //如果传了资产组字段
        if (!empty($registerData['group']))
        {
            //根据资产组绑定码查询资产所属资产组信息
            $groupInfo = Db::name('server_host_group')->where('check_code', $registerData['group'])->findOrEmpty();
            if(!$groupInfo){
                throw new CustomException('注册时失败:该group对应的资产组不存在', 422);
            }

            $group_id = $groupInfo['id'];
            //查询资产所属资产组模块授权界限值
            $limit = $authModuleModel->getClientModuleLimit($group_id);
            if($limit === null){
                throw new CustomException('注册时失败:请先对该资产所属资产组的client模块进行授权', 422);
            }else{
                //传了资产所属资产组 ，则以资产所属资产组数据为准
                $existNum = $relationModel->getMinAuthNum($group_id,ServerAuthModuleModel::CLIENT_MODULE_KEY);
                //传了资产所属资产组 ，则以资产所属资产组数据为准
                $etime = $authModuleModel->getClientModuleEtime($group_id);
            }

            //资产所属资产组id赋值
            empty($group_id) ? : $this->group_id = $group_id;

        }else{
            //没有传资产所属资产组时,查询整体界限值
            $limit = $authModuleModel->getClientModuleMostLimit();
            if($limit === null){
                throw new CustomException('注册时失败:服务端缺少client模块授权信息', 422);
            }
            //没有传资产所属资产组时,已绑定主机数=整体的已绑定主机数
            $existNum = $relationModel->getModuleAuthNum(ServerAuthModuleModel::CLIENT_MODULE_KEY);

            //没有传资产所属资产组时,授权结束时间 = 整体的授权结束时间
            $etime = $authModuleModel->getClientModuleMostEtime();
        }

        //判断主机数量是否超过了界限值
        $existNum = $existNum ? $existNum : 0;
        if($existNum >= $limit){
            throw new CustomException('注册时失败:主机数量已超过授权上限', 422);
        }

        //判断授权时间是否过期
        if($etime){
            if(time() > $etime){
                throw new CustomException('注册时失败:授权已经过期', 422);
            }
        }

        $host = [
            'name' => $registerData['alias'] . '-' . $this->first_ipaddr,
            'alias' => $registerData['alias'],
            'system_name' => $registerData['os'],
            'client_version' => $registerData['version'],
            'os' => stristr($registerData['os'], 'windows') ? 1 : 2,
            'group_id' => $this->group_id,
            'hostkey' => $this->host_key,
            'ctime' => time(),
            'ip' => $this->first_ipaddr
        ];

        //查询IP
        $ipInfo = Db::name('server_proxy')->where('type','client')->field('ip,port')->findOrEmpty();
        //学习数据
        $learnDataModel = new HostLearnDataModel();
        //访问日志
        $logModel = new HostAccessLogModel();


        Db::startTrans();
        try{
            $this->host_id = Db::name('Host')->insertGetId($host);
            if(!$this->host_id){
                Db::rollback();
                throw new CustomException('注册时失败:保存数据失败', 422);
            }

            $network_card = array_map(function($v){
                $v['host_id'] = $this->host_id;
                return $v;
            }, $this->network_card);

            if (!Db::name('HostContact')->insertAll($network_card)) {
                Db::rollback();
                throw new CustomException('注册时失败:保存数据失败', 422);
            }
            if (!Db::name('HostModuleRelation')->insert(['host_id'=>$this->host_id, 'client' => 1])) {
                Db::rollback();
                throw new CustomException('注册时失败:保存数据失败', 422);
            }

            //如果该主机clinet模块的连接信息不存在,则写入
            if(!Db::name('host_link')->where(['host_id'=>$this->host_id, 'module' => 'client'])->find()){
                if (!Db::name('host_link')->insert(['host_id'=>$this->host_id, 'module' => 'client','ip' => $ipInfo['ip'],'port' => $ipInfo['port']])) {
                    Db::rollback();
                    throw new CustomException('注册时失败:保存数据失败', 422);
                }
            }

            //主机硬件信息
            Db::name('host_hardware')->json(['info'])->insert(['host_id' => $this->host_id,'info'=> $this->hostInfo]);

            //创建数据库 --学习数据
            $createDatabaseResult = $learnDataModel->createChildDatabaseByRoot($this->host_id);
            //如果创建库失败,返回失败信息
            if(!$createDatabaseResult['result']){
                Db::rollback();
                throw new CustomException('注册时失败,学习数据子数据库创建失败',422);
            }

            //创建学习数据表
            if (!$learnDataModel->createTable($this->host_id)) {
                Db::rollback();
                throw new CustomException('注册时失败:创建学习数据表失败', 422);
            }

            //创建数据库 --访问日志
            $createDatabaseResult = $logModel->createChildDatabaseByRoot($this->host_id);
            if(!$createDatabaseResult['result']){
                Db::rollback();
                throw new CustomException('注册时失败,访问日志子数据库创建失败',422);
            }

            //创建访问日志表
            if(!$logModel->createTable($this->host_id)){
                Db::rollback();
                throw new CustomException('注册时失败:创建访问日志表失败', 422);
            }

            //保存数据
            Db::commit();
        }catch (Exception $e){
            Db::rollback();
            throw new CustomException('注册时失败:保存数据失败', 422);
        }

    }

    /**
     * 检测hostkey及group
     * @param string $hostkey 主机绑定码
     * @param string $group   资产组绑定码
     *
     * @throws CustomException
     * @author wx
     * @date 2020/12/22 13:11
     */
    public function hostKeyAndGroup ($hostkey,$group)
    {
        //主机表的查询字段
        $field = 'id, group_id, hostkey, alias, system_name, os, client_version';

        //如果传了hostkey
        if (!empty($hostkey))
        {
            //查询主机表数据
            $hostInfo = Db::name('host')->field($field)->where('hostkey', $hostkey)->find();
            if (empty($hostInfo))
            {
                throw new CustomException('注册时失败:主机绑定码错误', 422);
            }

            //如果既传了host_key 也 传了group 则二者的资产组id必须一致
            if($group){
                $groupInfo = Db::name('server_host_group')->where('check_code',$group)->find();
                if(!$groupInfo){
                    throw new CustomException('注册时失败:资产组绑定码错误', 422);
                }
                if($groupInfo['id'] != $hostInfo['group_id']){
                    throw new CustomException('注册时失败:传入的资产组绑定码与该主机已有的资产组绑定码不一致', 422);
                }
            }


            $this->host_key = $hostInfo['hostkey'];
        }
        else
        {
            //如果没有传hostkey,则group字段为必传
            if(!$group){
                throw new CustomException('注册时失败:资产绑定码为空时,资产组绑定码不能为空', 422);
            }
            //查询该资产组信息
            $groupInfo = Db::name('server_host_group')->where('check_code',$group)->find();
            if(!$groupInfo){
                throw new CustomException('注册时失败:资产组绑定码错误', 422);
            }

            $this->host_key = md5(implode(',', $this->mac) . '|'. implode(',', $this->ipaddr));
            $hostInfo = Db::name('host')->field($field)->where('hostkey', $this->host_key)->find();

            //如果是重新注册,则二者的group必须相等
            if($hostInfo){
                if($groupInfo['id'] != $hostInfo['group_id']){
                    throw new CustomException('注册时失败:传入的资产组绑定码与该主机原来的资产组绑定码不一致', 422);
                }
            }else{
                //全新注册且没有传hostkey,以新的group_id为准
                $groupId = $groupInfo['id'];
            }

        }
        //全新注册且没有传hostkey,以新的group_id为准,否则以原group_id为准
        $this->group_id = isset($groupId) ? $groupId :$hostInfo['group_id'];

        if (empty($hostInfo))
        {
            return;
        }

        //判断是否在线,在线则证明已经注册,不在线则可以注册
        $onlineCount = Db::name('host_link')
            ->where('host_id',$hostInfo['id'])
            ->where('module','client')
            ->where('status',1)
            ->count();

        if($onlineCount){
            throw new CustomException('注册时失败:该主机已被注册', 422);
        }
        $this->is_reinstall = true;
        $this->host_key = $hostInfo['hostkey'];
        $this->host_id = $hostInfo['id'];
        $this->host_info = $hostInfo;
    }

    /**
     * @name: 主机网卡地址
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-10-21 15:31:23
     */
    public function networkcard ($networkCard_a)
    {
        $ipaddr = [];
        $mac = [];
        foreach ($networkCard_a as $k => $v)
        {
            if ($k == 0)
            {
                $this->first_ipaddr = $v['ip'][0];
            }


            if(empty($v['ip'])){
                unset($networkCard_a[$k]);
                continue;
            }


            $networkCard_a[$k]['dns'] = empty($v['dns']) ? '' : implode(',', $v['dns']);
            $networkCard_a[$k]['ip'] = implode(',', $v['ip']);
            $networkCard_a[$k]['gateway'] = implode(',', $v['gateway']);
            $networkCard_a[$k]['network_type'] = isset($v['type']) ? $v['type'] : '';
            $ipaddr = array_merge($ipaddr, $v['ip']);
            $mac[] = $v['mac'];
            unset($networkCard_a[$k]['type']);
        }
        $this->network_card = $networkCard_a;
        $this->ipaddr = array_unique($ipaddr);
        $this->mac = $mac;
    }

    /**
     * @name: 主机详情
     * @Param: 
     * @Description: 
     * @Author: foo
     * @Date: 2020-10-21 15:31:17
     */
    public function hostInfo ($hostInfo_a)
    {
        if (!isset($hostInfo_a['cpu']) && !isset($hostInfo_a['cpuname']))
        {
            throw new CustomException('注册时失败:必须发送主机cpu信息', 422);
        }

        $this->cpu = $hostInfo_a['cpu'];
        $this->hostInfo = $hostInfo_a;
    }
}